package com.cn.web.user.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.SmUtil;
import com.cn.auth.AuthVo;
import com.cn.error.ErrorConstantUtil;
import com.cn.exception.MyException;
import com.cn.global.GlobalParamtersUtil;
import com.cn.lock.MyRedisLockUtil;
import com.cn.pwd.util.PwdCheckUtil;
import com.cn.pwd.vo.UserVo;
import com.cn.redis.RedisHandleUtil;
import com.cn.role.RoleDO;
import com.cn.user.UserDO;
import com.cn.user.UserDeviceDO;
import com.cn.util.*;
import com.cn.web.org.service.OrgService;
import com.cn.web.role.service.UserRoleService;
import com.cn.web.role.vo.RoleVO;
import com.cn.web.user.mapper.IUserMapper;
import com.cn.web.user.service.LoginService;
import com.cn.web.user.service.UserDeviceService;
import com.cn.web.user.service.UserService;
import com.cn.web.user.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * description: LoginService <br>
 * date: 23.5.25 16:40 <br>
 * author: cn_yaojin <br>
 * version: 1.0 <br>
 */
@Component
@Slf4j
public class LoginServiceImpl implements LoginService {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private OrgService orgService;

    @Autowired
    private IUserMapper userMapper;

    @Autowired
    public UserService userService;

    @Autowired
    private UserDeviceService userDeviceService;

    private static String LOCK_ACCOUNT_COUNT_KEY = "LOCK:ACCOUNT:COUNT:%s";

    private static String LOCK_ACCOUNT_KEY = "LOCK:ACCOUNT:%s";

    public static String md5(String data) {
        return SmUtil.sm3(data);
    }

    private String getLockAccountCountKey(String userId) {
        return String.format(LOCK_ACCOUNT_COUNT_KEY, userId);
    }

    private String getLockAccountKey(String userId) {
        return String.format(LOCK_ACCOUNT_KEY, userId);
    }

    private void lockAccount(Long userId, String lockKey) {
        String lockAccountCountKey = getLockAccountCountKey(userId.toString());
        long v = RedisHandleUtil.increment(lockAccountCountKey, 1L);
        var global = GlobalParamtersUtil.get();
        if (global != null) {
            if (global.getLockAccountCount() > 0 && global.getLockAccountCount() <= v && global.getLockAccountSeconds() > 0) {
                //锁定账号
                RedisHandleUtil.add(getLockAccountKey(lockKey), v + "", global.getLockAccountSeconds());
                RedisHandleUtil.del(lockAccountCountKey);
            }
            //设置过期时间（跟锁定时间保持一致）
            RedisHandleUtil.expire(lockAccountCountKey, global.getLockAccountSeconds());
        }
    }

    /**
     * 检测账号是否被锁定
     *
     * @param lockKey
     */
    private void checkLockAccount(String lockKey) {
        String v = RedisHandleUtil.get(getLockAccountKey(lockKey));
        if (StringUtils.isNotEmpty(v)) {
            long v1 = Long.valueOf(v);
            var global = GlobalParamtersUtil.get();
            if (global.getLockAccountCount() > 0 && v1 >= global.getLockAccountCount()) {
                throw new MyException(ConstanUserUtil.USER_LOCK_ERROR);
            }
        }
    }

    public String getPhoneTokenIdByDeviceId(UserDeviceDO deviceDO) {
        String txt = String.format("%s_%d_%d", deviceDO.getDeviceId(), deviceDO.getUserId(), System.currentTimeMillis());
        return md5(txt);
    }

    /**
     * 校验用户是否存在
     *
     * @param info
     */
    private void checkUserExist(UserRegisterPcDTO info) {
        if (!info.getPassWord().equals(info.getPassWord2())) {
            throw new MyException(ConstanUserUtil.PASSWORD_CONFIRM_ERROR);
        }
        checkPhoneCode(info.getPhone(), info.getCode());
        //1.检测账号是否已经被注册
        //2.检测手机号是否已经被注册
        Map<String, Object> pd = new HashMap<>();
        pd.put("userType", 2);
        pd.put("accountName", info.getUserName());
        long count = this.userMapper.checkUserExist(pd);
        if (count > 0) {
            throw new MyException(ConstanUserUtil.ACCOUNT_EXIST_ERROR);
        }
        pd.remove("accountName");
        pd.put("phone", info.getPhone());
        count = this.userMapper.checkUserExist(pd);
        if (count > 0) {
            throw new MyException(ConstanUserUtil.PHONE_EXIST_ERROR);
        }

    }

    /**
     * 校验短信验证码
     *
     * @param phone
     * @param code
     */
    private void checkPhoneCode(String phone, String code) {

    }

    private String getRegisterKey(String phone) {
        return String.format(ConstanUserUtil.REGISTER_LOCK_KEY, phone);
    }

    @Transactional
    @Override
    public Object register(UserRegisterPcDTO info) throws InterruptedException {
        //校验密码复杂度
        UserVo passUser = new UserVo();
        passUser.setAccount(info.getUserName());//账号
        passUser.setPhone(info.getPhone());//手机号
        PwdCheckUtil.evalPwd(info.getPassWord(), passUser);
        //加锁，防止多次重复注册
        String key = getRegisterKey(info.getPhone());
        RLock rLock = MyRedisLockUtil.getFairLock(key);
        if (rLock.tryLock(1, 20, TimeUnit.SECONDS)) {
            try {
                checkUserExist(info);

                UserDO userDO = new UserDO();
                userDO.setUserType(2).setUserItemType(1);//表示外部用户
                userDO.setAccountName(info.getUserName())
                        .setPhone(info.getPhone())
                        .setPassword(md5(info.getPassWord()))
                        .setSuperFlag("1")//普通用户
                        .setValidateFlag("1");//没有实名

                userDO.setAddTime(DateUtil.getTime())
                        .setRegisterIp(IPUtil.getIp(request))
                        .setUserSlotId(UserServiceImpl.incrementUserSlotId().intValue())
                        .setId(SnowflakeIdWorkerUtil.getId())
                ;

                //保存用户信息
                this.userMapper.insert(userDO);

                //将以下参数设置为null
                userDO.setRoles(null).setOrgs(null).setMenuPermission(null).setPassword(null)
                        .setUserName("")
                        .setCardNum("")
                        .setWorkNum("")
                        .setWorkPost("")
                        .setUserStatus("0")
                        .setPhoto("")
                        .setSuperFlag("1")
                        .setLastLoginTime("")
                        .setLoginIp("")
                ;
            } finally {
                rLock.unlock();//释放锁
            }

        } else {
            throw new MyException(ConstanUserUtil.REPEAT_ERROR);
        }

        return null;
    }

    /**
     * 用户名、密码登录
     *
     * @param userLogin
     * @param loginDeviceTypeEnum
     * @return
     */
    @Override
    public AuthVo<UserDO> login(UserLoginDTO userLogin, String loginDeviceTypeEnum) {
        String lockKey = userLogin.getUserName() + userLogin.getUserType();
        checkLockAccount(lockKey);
        var tmp = this.userMapper.findByUserName(userLogin);
        if (tmp == null) {
            throw new MyException(ErrorConstantUtil.USER_NO_EXSIT);
        }
        if (!tmp.getPassword().equalsIgnoreCase(md5(userLogin.getPassWord()))) {
            lockAccount(tmp.getId(), lockKey);
            throw new MyException(ErrorConstantUtil.LONG_USER_NAME_ERROR);
        }

        return packUser2Login(tmp, loginDeviceTypeEnum);
    }

    @Override
    public AuthVo<UserDO> loginByPcCode(UserLoginByPcCodeDTO info) {
        //验证短信码是否正确
        checkPhoneCode(info.getPhone(), info.getCode());

        UserDO tmp = this.userMapper.findByUserPhone(info);
        if (tmp == null) {
            throw new MyException(ErrorConstantUtil.USER_NO_EXSIT);
        }

        return packUser2Login(tmp, LoginDeviceTypeEnum.pc.name());
    }

    @Override
    public AuthVo<UserDO> loginRamCode(UserLoginByRAMDTO info) {
        UserChildAccountDO ramUser = new UserChildAccountDO();
        ramUser.setAccountName(info.getUserName());
        ramUser.setAccountType(Integer.valueOf(info.getAccountType()));
        return null;
    }

    @Override
    public void forgetPass(UserForgetPassDTO info) {
        if (!info.getPassWord().equals(info.getPassWord2())) {
            throw new MyException(ErrorConstantUtil.PASS_WORD_CONFIRM_ERROR);
        }
        UserLoginByPhoneCodeDTO userLogin = new UserLoginByPhoneCodeDTO();
        userLogin.setUserType("2").setPhone(info.getPhone());
        var tmp = this.userMapper.findByUserPhone(userLogin);
        if (tmp == null) {
            throw new MyException(ErrorConstantUtil.USER_NO_EXSIT);
        }
        tmp.setPassword(md5(info.getPassWord()));
        this.userMapper.updatePassWord(tmp);

    }

    @Transactional
    @Override
    public AuthVo<UserDO> loginByPhone(UserLoginByPhoneDTO info) {
        String lockKey = info.getUserName() + info.getUserType();
        checkLockAccount(lockKey);
        var tmp = this.userMapper.findByUserName(info);
        if (tmp == null) {
            throw new MyException(ErrorConstantUtil.USER_NO_EXSIT);
        }
        if (!tmp.getPassword().equalsIgnoreCase(md5(info.getPassWord()))) {
            //账号密码错误，限制登录次数
            lockAccount(tmp.getId(), lockKey);
            throw new MyException(ErrorConstantUtil.LONG_USER_NAME_ERROR);
        }
        //往设备信息表里面新增记录
        UserDeviceDO deviceDO = new UserDeviceDO();
        deviceDO.setUserId(tmp.getId());
        deviceDO.setDeviceName(info.getDeviceName());
        deviceDO.setDeviceId(info.getDeviceId());
        deviceDO.setLoginIp(IPUtil.getIp(request));
        deviceDO.setTokenId(getPhoneTokenIdByDeviceId(deviceDO));
        try {
            this.userDeviceService.add(deviceDO);
        } catch (InterruptedException e) {

        }

        return packUser2Login(tmp, LoginDeviceTypeEnum.mobile.name()).setOpenId(deviceDO.getTokenId());
    }

    @Transactional
    @Override
    public AuthVo<UserDO> loginByPhoneCode(UserLoginByPhoneCodeDTO info) {
        //验证短信码是否正确
        checkPhoneCode(info.getPhone(), info.getCode());

        UserDO tmp = this.userMapper.findByUserPhone(info);
        if (tmp == null) {
            throw new MyException(ErrorConstantUtil.USER_NO_EXSIT);
        }
        //往设备信息表里面新增记录
        UserDeviceDO deviceDO = new UserDeviceDO();
        deviceDO.setUserId(tmp.getId());
        deviceDO.setDeviceName(info.getDeviceName());
        deviceDO.setDeviceId(info.getDeviceId());
        deviceDO.setLoginIp(IPUtil.getIp(request));
        deviceDO.setTokenId(getPhoneTokenIdByDeviceId(deviceDO));
        try {
            this.userDeviceService.add(deviceDO);
        } catch (InterruptedException e) {

        }

        return packUser2Login(tmp, LoginDeviceTypeEnum.mobile.name()).setOpenId(deviceDO.getTokenId());
    }

    @Override
    public AuthVo<UserDO> loginByPhoneDeviceIdTokenId(UserLoginByPhoneTokenDTO info) {
        UserDO user = this.userMapper.findByPhoneDeviceIdTokenId(info);
        if (user == null) {
            throw new MyException(ErrorConstantUtil.USER_NO_EXSIT);
        }
        return packUser2Login(user, LoginDeviceTypeEnum.mobile.name()).setOpenId(info.getTokenId());
    }

    @Override
    public AuthVo<UserDO> loginCode(String userId, String loginDeviceTypeEnum) {
        var tmp = this.userMapper.selectById(userId);
        return packUser2Login(tmp, loginDeviceTypeEnum);
    }

    private AuthVo<UserDO> packUser2Login(UserDO tmp, String loginDeviceTypeEnum) {
        if (tmp.getUserStatus().equals("1")) {
            throw new MyException(ErrorConstantUtil.LONG_USER_DISABLED_ERROR);
        }
        tmp.setPassword(null);

        if (tmp.getUserType() == 1) {
            //封装菜单数据权限
            tmp.setMenuPermission(this.userRoleService.packMenuPermission(tmp.getId().toString()));

            //查询登录人拥有的角色
            List<RoleVO> tmpRole = this.userRoleService.listRoleByUserId(tmp.getId().toString());
            List<RoleDO> roles = new ArrayList<>();
            if (tmpRole != null) {
                for (RoleVO t : tmpRole) {
                    RoleDO role = new RoleDO();
                    BeanUtils.copyProperties(t, role);
                    roles.add(role);
                }
            }
            tmp.setRoles(roles);

            //查询机构信息
            var orgs = orgService.listUnitByUserId(tmp.getId().toString());
            tmp.setOrgs(orgs);
        }

        //登录缓存
        StpUtil.login(tmp.getId(), loginDeviceTypeEnum);
        //设置session
        StpUtil.getSession().set(BaseController.sessionKey, tmp);
        //更新存续时间
        response.setHeader("set-cookie", null);
        AuthVo auth = new AuthVo();
        auth.setUserName(tmp.getUserName())
                .setUser(tmp)
                .setToken(StpUtil.getTokenValue());

        auth.setLastLoginTime(tmp.getLastLoginTime())
                .setPhoto(tmp.getPhoto())
                .setId(tmp.getId().toString());
        if (StringUtils.isNotEmpty(tmp.getWorkPost())) {
            auth.setWorkPost(tmp.getWorkPost());
        }
        var global = GlobalParamtersUtil.get();
        if (global != null) {
            String domain = null;
            if (tmp.getUserType() == 1) {
                domain = global.getAdminCookieDomain();
            } else {
                domain = global.getCookieDomain();
            }

            auth.setLoginUrl(global.getLoginUrl())
                    .setFileDomain(global.getFileDomain())
                    .setCookieDomain(domain)
            ;
        }
        tmp.setLastLoginTime(DateUtil.getTime())
                .setLoginIp(IPUtil.getIp(request));

        this.userMapper.updateById(tmp);

        addHeader(StpUtil.getTokenName(), auth.getToken());
        return auth;
    }

    /**
     * 登录成功后，新增token通请求头中
     *
     * @param name
     * @param value
     */
    public void addHeader(String name, String value) {
        HeaderMapRequestWrapper headerMapRequestWrapper = new HeaderMapRequestWrapper(request);
        headerMapRequestWrapper.addHeader(name, value);
    }

}
